use crate::{block::Block, geometry::Coord, level::Level, moment::Moment};
use crossterm::{
    cursor::{Hide, MoveTo, Show},
    style::{Color, ContentStyle, PrintStyledContent, StyledContent, Stylize},
    terminal::{Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen},
    QueueableCommand,
};
use itertools::repeat_n;
use smol::{prelude::*, Timer};
use std::{
    io::{stdout, Result, StdoutLock, Write},
    iter::once,
    pin::Pin,
    time::Duration,
};

const TITLE: &str = "2048 by ZengYL";
const TITLE_H: u16 = 3;
const SCORE_H: u16 = 3;
const HEADER_H: u16 = TITLE_H + SCORE_H;
const BLOCK_H: u16 = 3;
const BLOCK_W: u16 = BLOCK_H * 2;
const GAP_H: u16 = 1;
const GAP_W: u16 = GAP_H * 2;

const TICKS: i32 = 6;
const INTERVAL: Duration = Duration::from_millis(10);

const DARK_BG_C: Color = rgb(0x3C3A32);
const TXT_C: Color = rgb(0x776E65);
const TXT_WHITE_C: Color = rgb(0xF9F6F2);
const TITLE_BG_C: Color = rgb(0xFFDD77);
const OVER_BG_C: Color = rgb(0xF75E3E);
const BOARD_GAP_C: Color = rgb(0x948371);
const BOARD_BG_C: Color = rgb(0xADA090);
/// 第`n`项存储`2^(n+1)`的颜色
const BLOCK_BG_C: &[Color] = &[
    rgb(0xEEE4DA), // 2
    rgb(0xE4DAC1), // 4
    rgb(0xF2A179), // 8
    rgb(0xF58563), // 16
    rgb(0xF66C5F), // 32
    rgb(0xF64E3B), // 64
    rgb(0xE4BF62), // 128
    rgb(0xE4BC51), // 256
    rgb(0xE4B840), // 512
    rgb(0xE4B52F), // 1024
    rgb(0xE4B21E), // 2048
];
const BLOCK_DF_BG_C: Color = DARK_BG_C;

pub struct Renderer<W: Write = StdoutLock<'static>> {
    writer: W,
    bg_str: String,
}

const fn rgb(hex: u32) -> Color {
    Color::Rgb {
        r: (hex >> 16) as u8,
        g: (hex >> 8) as u8,
        b: hex as u8,
    }
}

fn interpolation(a: i32, b: i32, tick: i32) -> i32 {
    a + (tick * (b - a)) / TICKS
}

fn block_style(level: Level) -> ContentStyle {
    let fg_dark = level.exp.get() < 3;
    let fg = if fg_dark { TXT_C } else { TXT_WHITE_C };
    let &bg = BLOCK_BG_C
        .get(level.exp.get() as usize - 1)
        .unwrap_or(&BLOCK_DF_BG_C);
    ContentStyle::new().bold().with(fg).on(bg)
}

fn width(size: Coord) -> u16 {
    GAP_W + size.x as u16 * (BLOCK_W + GAP_W)
}

fn init(writer: &mut impl Write, size: Coord) -> Result<()> {
    let width = width(size);
    let title = {
        let w = width as usize - 2;
        format!("  {s:w$}\n  {TITLE:w$}\n  {s:w$}\n", s = "")
    };

    writer
        // 初始化
        .queue(EnterAlternateScreen)?
        .queue(Clear(ClearType::All))?
        .queue(Hide)?
        // 显示标题
        .queue(MoveTo(0, 0))?
        .queue(PrintStyledContent(title.with(TXT_C).on(TITLE_BG_C)))?
        .flush()?;

    Ok(())
}

fn background_str(size: Coord) -> String {
    let gap = repeat_n('█', width(size) as usize);
    let block = {
        let gap = repeat_n('█', GAP_W as usize);
        let block = repeat_n(' ', BLOCK_W as usize);
        repeat_n(gap.clone().chain(block), size.x)
            .flatten()
            .chain(gap)
    };
    let blocks = repeat_n(block.chain(once('\n')), BLOCK_H as usize).flatten();
    repeat_n(gap.clone().chain(once('\n')).chain(blocks), size.y)
        .flatten()
        .chain(gap)
        .collect()
}

impl Renderer {
    pub fn new(size: Coord) -> Result<Self> {
        Self::with_writer(stdout().lock(), size)
    }
}

impl<W: Write> Renderer<W> {
    pub fn with_writer(mut writer: W, size: Coord) -> Result<Self> {
        init(&mut writer, size)?;
        let bg_str = background_str(size);
        Ok(Self { writer, bg_str })
    }

    pub fn score(&mut self, size: Coord, score: u64, over: bool) -> Result<()> {
        let score = {
            let w = width(size) as usize;
            format!("{s:w$}\n  Score{:w2$}  \n{s:w$}", score, s = "", w2 = w - 9)
        };
        let color = if over { OVER_BG_C } else { DARK_BG_C };

        self.writer
            .queue(MoveTo(0, TITLE_H))?
            .queue(PrintStyledContent(score.bold().with(TXT_WHITE_C).on(color)))?
            .flush()?;

        Ok(())
    }

    fn block(&mut self, tick: i32, block: &Block) -> Result<()> {
        let (cols, rows) = match block.dst() {
            None => (
                block.pos.x as u16 * (BLOCK_W + GAP_W),
                block.pos.y as u16 * (BLOCK_H + GAP_H),
            ),
            Some(dst) => {
                let i = |a: usize, b: usize, f: i32| {
                    interpolation(a as i32 * f, b as i32 * f, tick) as u16
                };
                (
                    i(block.pos.x, dst.x, (BLOCK_W + GAP_W) as i32),
                    i(block.pos.y, dst.y, (BLOCK_H + GAP_H) as i32),
                )
            }
        };

        let space = &*" ".repeat(BLOCK_W as usize);
        let level = &*format!(" {:^w$} ", block.level, w = BLOCK_W as usize - 2);
        let style = block_style(block.level);

        let lines = if block.is_new() {
            interpolation(0, BLOCK_H as i32, tick) as u16
        } else {
            BLOCK_H
        };
        for line in 0..lines {
            let content = if line == BLOCK_H / 2 { level } else { space };
            self.writer
                .queue(MoveTo(GAP_W + cols, GAP_H + HEADER_H + rows + line))?
                .queue(PrintStyledContent(StyledContent::new(style, content)))?;
        }
        self.writer.flush()?;

        Ok(())
    }

    fn background(&mut self) -> Result<()> {
        self.writer
            .queue(MoveTo(0, HEADER_H))?
            .queue(PrintStyledContent(
                self.bg_str.as_str().with(BOARD_GAP_C).on(BOARD_BG_C),
            ))?
            .flush()?;
        Ok(())
    }

    fn blocks<'a>(
        &mut self,
        tick: i32,
        blocks: impl Iterator<Item = &'a Block> + Clone,
    ) -> Result<()> {
        self.background()?;
        // 新生成的要在最上面
        let new = blocks.clone().filter(|&x| x.is_new());
        let old = blocks.filter(|&x| !x.is_new());
        for block in old.chain(new) {
            self.block(tick, block)?;
        }
        Ok(())
    }

    pub async fn anim_blocks<'a>(
        &mut self,
        blocks: impl Iterator<Item = &'a Block> + Clone,
        stop: Pin<&mut impl Moment>,
    ) -> Result<()> {
        let stop = async {
            stop.await;
            Result::Ok(()) // type hint
        };
        let anim = async {
            for tick in 0..TICKS {
                self.blocks(tick, blocks.clone())?;
                Timer::after(INTERVAL).await;
            }
            Ok(())
        };
        stop.or(anim).await?;
        self.blocks(TICKS, blocks)?;
        Ok(())
    }
}

/// 使用析构函数，可以保证panic时也正确恢复屏幕
impl<W: Write> Drop for Renderer<W> {
    fn drop(&mut self) {
        // 不处理错误。如果这都错误了，那也没啥办法。
        let _: Result<_> = (|| {
            self.writer
                .queue(LeaveAlternateScreen)?
                .queue(Show)?
                .flush()?;
            Ok(())
        })();
    }
}
